package com.stars.easyms.redis.interceptor;

import com.stars.easyms.redis.annotion.RedisHashGet;
import com.stars.easyms.redis.exception.RedisInterceptorException;
import com.stars.easyms.base.util.GenericTypeUtil;
import com.stars.easyms.base.util.ReflectUtil;
import com.stars.easyms.base.util.TimeUtil;
import org.aopalliance.intercept.MethodInvocation;

import java.lang.reflect.Method;
import java.lang.reflect.ParameterizedType;
import java.lang.reflect.Type;
import java.util.HashMap;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.TimeUnit;

/**
 * <p>className: RedisHashGetInterceptor</p>
 * <p>description: RedisHashGet注解拦截器</p>
 *
 * @author guoguifang
 * @version 1.2.1
 * @date 2019-03-18 14:49
 */
public class RedisHashGetInterceptor extends AbstractRedisInterceptor<RedisHashGet> {

    @SuppressWarnings("unchecked")
    @Override
    protected Object invoke(MethodInvocation methodInvocation, RedisHashGet redisHashGet) throws RedisInterceptorException {
        Object result = null;
        if (isEnabled()) {
            Method pointMethod = methodInvocation.getMethod();
            Class<?> returnType = pointMethod.getReturnType();
            Type genericReturnType = pointMethod.getGenericReturnType();
            Set<String> hashKeySet = getRedisHashKey(methodInvocation);
            int hashKeySetSize = hashKeySet.size();
            boolean isInvalidReturnType = hashKeySetSize == 0 && Map.class.isAssignableFrom(returnType)
                    && !String.class.isAssignableFrom(GenericTypeUtil.getGenericClass(genericReturnType, 0));
            if (isInvalidReturnType) {
                logger.error("Method '{}' when have no redis hashKey and returnType is Map, the returnType should be Map<String, T>!", ReflectUtil.getMethodFullName(pointMethod));
            } else {
                isInvalidReturnType = hashKeySetSize > 1 && (!Map.class.isAssignableFrom(returnType) || !String.class.isAssignableFrom(GenericTypeUtil.getGenericClass(genericReturnType, 0)));
                if (isInvalidReturnType) {
                    logger.error("Method '{}' when have more than one redis field, returnType should be Map<String, T>!", ReflectUtil.getMethodFullName(pointMethod));
                } else {
                    String redisKey = getRedisKey(methodInvocation, redisHashGet.group());
                    if (hashKeySetSize == 1) {
                        String hashKey = (String) hashKeySet.toArray()[0];
                        if (!(genericReturnType instanceof ParameterizedType)) {
                            result = easyMsRedisTemplate.hashGet(redisKey, hashKey, returnType);
                        } else {
                            result = GenericTypeUtil.parseObject(easyMsRedisTemplate.hashGet(redisKey, hashKey), (ParameterizedType) genericReturnType, returnType);
                        }
                    } else if (Map.class.isAssignableFrom(returnType)) {
                        ParameterizedType returnMapValueType = GenericTypeUtil.getGenericType(genericReturnType, 1);
                        Class returnMapValueClass = GenericTypeUtil.getGenericClass(genericReturnType, 1);
                        if (returnMapValueType == null) {
                            if (hashKeySetSize == 0) {
                                result = easyMsRedisTemplate.hashGetAll(redisKey, returnMapValueClass);
                            } else {
                                result = easyMsRedisTemplate.hashGet(redisKey, hashKeySet, returnMapValueClass);
                            }
                        } else {
                            Map<String, String> redisMap;
                            if (hashKeySetSize == 0) {
                                redisMap = easyMsRedisTemplate.hashGetAll(redisKey);
                            } else {
                                redisMap = easyMsRedisTemplate.hashGet(redisKey, hashKeySet);
                            }
                            if (redisMap != null && redisMap.size() > 0) {
                                Map resultMap = new HashMap<>(redisMap.size());
                                redisMap.forEach((key, value) -> resultMap.put(key, GenericTypeUtil.parseObject(value, returnMapValueType, returnMapValueClass)));
                                result = resultMap;
                            }
                        }
                        if (result != null && hashKeySetSize > 0 && hashKeySetSize != ((Map) result).size()) {
                            result = null;
                        }
                    } else {
                        result = easyMsRedisTemplate.hashGet(redisKey, returnType);
                    }
                    if (result != null) {
                        return result;
                    }
                    result = proceed(methodInvocation);
                    if (result != null) {
                        if (hashKeySetSize == 1) {
                            easyMsRedisTemplate.hashSet(redisKey, (String) hashKeySet.toArray()[0], result);
                        } else {
                            easyMsRedisTemplate.hashSet(redisKey, result);
                        }
                        Long expire = getExpire(methodInvocation);
                        expire = expire != null ? expire : TimeUtil.parseToMilliseconds(redisHashGet.expire());
                        if (expire != null && expire > 0) {
                            easyMsRedisTemplate.expire(redisKey, expire, TimeUnit.MILLISECONDS);
                        }
                    }
                }
            }
        } else {
            result = proceed(methodInvocation);
        }
        return result;
    }
}